#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define KEY_NAME  "key" /* 设备名字 */
#define KEY_COUNT  1    /* 设备号个数 */
// 定义按键值
#define KEY_0_VALUE    0xf0    // KEY0 的按键值
#define INVAKEY        0x00    // 无效的按键值

// key 设备结构体
struct key_dev {
    dev_t devid;
    int major;
    int minor;

    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;

    int gpio;
    atomic_t key_value;
};
struct key_dev key;

int key_open (struct inode *node, struct file *filep)
{
    printk(" open !\r\n");
    filep->private_data = &key;
    return 0;
}

ssize_t key_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    unsigned char value;
    if(gpio_get_value(key.gpio) != 0)  // 按键按下
    {
        while(gpio_get_value(key.gpio));  // 等待案件释放
        atomic_set(&key.key_value, KEY_0_VALUE);
    }
    else
    {
        atomic_set(&key.key_value, INVAKEY);
    }

    value = atomic_read(&key.key_value);
    ret = __copy_to_user(buf, &value, sizeof(value));

    return ret;
}

int key_release (struct inode *node, struct file *filep)
{
    struct key_dev *dev = filep->private_data;
    dev = NULL;
    printk(" release !\r\n");

    return 0;
}

static struct file_operations key_ops = {
    .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
    .release = key_release,
};


static int __init key_init(void)
{
    int ret = 0;
    printk(" init !\r\n");

    // 初始化原子变量
    atomic_set(&key.key_value, INVAKEY);

    /* 1、注册设备号 */
    key.major = 0;
    if(key.major){
        ret = register_chrdev_region(key.devid, KEY_COUNT, KEY_NAME);
    }
    else{
        ret = alloc_chrdev_region(&key.devid, 0, KEY_COUNT, KEY_NAME);
        key.major = MAJOR(key.devid);
        key.minor = MINOR(key.devid);
    }
    if(ret < 0){
        goto fail_devid;
    }
    printk("key.major = %d, key.minor = %d\r\n", key.major, key.minor);

    cdev_init(&key.cdev, &key_ops);
    ret = cdev_add(&key.cdev, key.devid, KEY_COUNT);
    if(ret < 0){
        goto fail_cdev;
    }

    key.class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(key.class)){
        ret = PTR_ERR(key.class);
        goto fail_class;
    }

    key.device = device_create(key.class, NULL, key.devid, NULL,KEY_NAME);
    if(IS_ERR(key.device)){
        ret = PTR_ERR(key.device);
        goto fail_device;
    }

    /* 获取设备树节点 */
    key.nd = of_find_node_by_path("/key");
    if(key.nd == NULL) {
        printk("Can't find key node !\r\n");
        ret = -EIO;
        goto fail_node;
    }

    /* 获取 key 对应的 GPIO */
    key.gpio = of_get_named_gpio(key.nd, "key-gpios", 0);
    printk("key gpio num = %d\r\n", key.gpio);

    ret = gpio_request(key.gpio, "key0");

    gpio_direction_input(key.gpio);

    return 0;

fail_node:
    device_destroy(key.class, key.devid);
fail_device:
    class_destroy(key.class);
fail_class:
    cdev_del(&key.cdev);
fail_cdev:
    unregister_chrdev_region(key.devid, KEY_COUNT);
fail_devid:
    return ret;
}

static void __exit key_exit(void)
{
    gpio_free(key.gpio);
    device_destroy(key.class, key.devid);
    class_destroy(key.class);
    cdev_del(&key.cdev);
    unregister_chrdev_region(key.devid, KEY_COUNT);

    printk(" exit !\r\n");
}


module_init(key_init);
module_exit(key_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("george <g_yubo@163.com>");